#include "zview_app.hpp"
#include <malloc.h>
#include <string>
#include <unistd.h>
#include <vector>
#include <cstring>

#include <imgui/memory_editor.hpp>

#include <sys/time.h>

constexpr int MEM_SIZE = 32 << 20;

ZViewApp::ZViewApp(GLFWwindow *w, const char *name) : App(w, name) {
}

ZViewApp::~ZViewApp() {
}

void ZViewApp::OnInitialize() {
    pBuff1 = memalign(getpagesize(), MEM_SIZE);
    pBuff2 = memalign(getpagesize(), MEM_SIZE);
    uint8_t *p = (uint8_t *) pBuff1;
    for (int i = 0; i < MEM_SIZE; ++i) {
        *p++ = i;
    }
    pDev = nullptr;

    test_cycle = 10;
    test_size = MEM_SIZE;

    snprintf(h2c_result, sizeof(h2c_result), "N/A");
    snprintf(c2h_result, sizeof(c2h_result), "N/A");

    App::OnInitialize();
}

void ZViewApp::OnCleanup() {
    App::OnCleanup();
    free(pBuff1);
    free(pBuff2);
    if (h2c.joinable())
        h2c.join();
    if (c2h.joinable())
        c2h.join();
    if (pDev) {
        pDev->Close();
        delete pDev;
        pDev = nullptr;
    }
}

void ZViewApp::RenderFrame(double delta_time, double total_time) {
}


static const char *device_names[]{
    "A",
    "B"
};

void ZViewApp::StandaloneUI() {
    using namespace ImGui;

    if (Begin("ZView Device", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
        AlignTextToFramePadding();
        TextUnformatted("ZView Device:");
        ImGui::AlignTextToFramePadding();
        ImGui::SameLine();

        static int m_select_item = 0;

        auto Devs = DeviceTools::GetDevInfos();
        std::vector<std::string> vecSelect;

        for (auto &dev: Devs) {
            vecSelect.push_back(dev.strName);
        }
        if (vecSelect.empty())
        {
            TextColored(ImVec4(1.f,1.f,0.f,1.f), "No Device Found!!");
            End();
            return;
        }
        if (ImGui::BeginCombo("##devs", vecSelect[m_select_item].c_str())) {
            for (int i = 0; i < std::size(vecSelect); ++i) {
                const char *pDev = vecSelect[i].c_str();
                if (ImGui::Selectable(pDev)) {
                    m_select_item = i;
                    // m_data_changed_ = true;
                }
                if (i == m_select_item)
                    ImGui::SetItemDefaultFocus();
            }
            ImGui::EndCombo();
        }

        ImGui::InputText("Dev Name", &Devs[m_select_item].strName, ImGuiInputTextFlags_ReadOnly);
        ImGui::InputText("Dev Type", &Devs[m_select_item].strType, ImGuiInputTextFlags_ReadOnly);
        ImGui::InputText("Dev Desc", &Devs[m_select_item].strDisciptor, ImGuiInputTextFlags_ReadOnly);
        ImGui::InputText("Dev Id", &Devs[m_select_item].strId, ImGuiInputTextFlags_ReadOnly);

        static int32_t v = 0;
        static int32_t bar = 0;
        static int32_t offset = 0;
        ImGui::InputInt("bar", &bar);
        ImGui::InputInt("offset", &offset, 4, 100, 6);
        ImGui::InputInt("value", &v, 1, 100, 6);

        if (pDev) {
            uint8_t v8 = v;
            if (Button("Write -  8Bit")) {
                pDev->Write(bar, offset, &v8, sizeof(v8));
            }
            ImGui::SameLine();
            if (Button("Read -  8Bit")) {
                pDev->Read(bar, offset, &v8, sizeof(v8));
                v = v8;
                delete pDev;
            }

            uint16_t vv = v;
            if (Button("Write - 16Bit")) {
                pDev->Write(bar, offset, &vv, sizeof(vv));
            }
            ImGui::SameLine();
            if (Button("Read - 16Bit")) {
                pDev->Read(bar, offset, &vv, sizeof(vv));
                v = vv;
            }

            uint32_t v32 = v;
            if (Button("Write - 32Bit")) {
                pDev->Write(bar, offset, &v32, sizeof(v32));
            }
            ImGui::SameLine();
            if (Button("Read - 32Bit")) {
                pDev->Read(bar, offset, &v32, sizeof(v32));
                v = v32;
            }

            if (Button("DMA H2C")) {
                if (h2c.joinable())
                    h2c.join();
                h2c = std::thread([this]{ pDev->DmaH2C((void*)4096, pBuff1, MEM_SIZE); });
            }
            ImGui::SameLine();
            if (Button("DMA C2H")) {
                if (c2h.joinable())
                    c2h.join();
                c2h = std::thread([this] { pDev->DmaC2H(pBuff2, 0, MEM_SIZE); });
            }

            Separator();
            TextUnformatted("Test Cycles:");
            SameLine();
            InputInt("##Cycles", &test_cycle); test_cycle = std::clamp(test_cycle, 1, 100);
            TextUnformatted("Block Size(KB):");
            int kb = test_size >> 10;
            SameLine();
            InputInt("##Size", &kb); kb = std::clamp(kb, 1, 32768); test_size = kb << 10;
            if (Button("H2C Speed Test")) {
                SpeedTestForH2C();
            }
            SameLine();
            Text("Result: %s", h2c_result);
            if (Button("C2H Speed Test")) {
                SpeedTestForC2H();
            }
            SameLine();
            Text("Result: %s", c2h_result);
        }
        Separator();
        if (pDev && Button("Close")) {
            pDev->Close();
            delete pDev; pDev = nullptr;
        } else if (!pDev && Button("Open")) {
            pDev = DeviceTools::CreatDev(Devs[m_select_item]);
            pDev->Open();
            pDev->UseMap(true);
            pDev->MapMem({0,1});
        }
        Separator();
        TextUnformatted("Buffer Controls");
        if (Button("Clear Buffer 1")) memset(pBuff1, 0, MEM_SIZE);
        if (Button("Clear Buffer 2")) memset(pBuff2, 0, MEM_SIZE);
    }
    End();
    static MemoryEditor s1;
    static MemoryEditor s2;
    static MemoryEditor s3; s3.Cols =16;
    static MemoryEditor s4; s4.Cols =16;

    s1.DrawWindow("Buff 1", pBuff1, 1024);
    s2.DrawWindow("Buff 2", pBuff2, 1024);

    if (pDev) {
        s2.DrawWindow("Bar 0", pDev->GetMapMemAddr(0), 1024);
        s2.DrawWindow("Bar 1", pDev->GetMapMemAddr(1), 0x6100);
    }
}

static int timespec_check(struct timespec *t)
{
	if ((t->tv_nsec < 0) || (t->tv_nsec >= 1000000000))
		return -1;
	return 0;

}

static void timespec_sub(struct timespec *t1, struct timespec *t2)
{
	if (timespec_check(t1) < 0) {
		fprintf(stderr, "invalid time #1: %lld.%.9ld.\n",
			(long long)t1->tv_sec, t1->tv_nsec);
		return;
	}
	if (timespec_check(t2) < 0) {
		fprintf(stderr, "invalid time #2: %lld.%.9ld.\n",
			(long long)t2->tv_sec, t2->tv_nsec);
		return;
	}
	t1->tv_sec -= t2->tv_sec;
	t1->tv_nsec -= t2->tv_nsec;
	if (t1->tv_nsec >= 1000000000) {
		t1->tv_sec++;
		t1->tv_nsec -= 1000000000;
	} else if (t1->tv_nsec < 0) {
		t1->tv_sec--;
		t1->tv_nsec += 1000000000;
	}
}


void ZViewApp::SpeedTestForH2C()
{
    if (h2c.joinable())
        h2c.join();
    h2c = std::thread([this,cnt=test_cycle,size=test_size]{
        struct timespec ts_start, ts_end;
        unsigned long us = 0;
        for (int i=0; i<cnt; ++i) {
            clock_gettime(CLOCK_MONOTONIC, &ts_start);
            this->pDev->DmaH2C((void*)(intptr_t)(i*size), this->pBuff1, size);
            clock_gettime(CLOCK_MONOTONIC, &ts_end);
            timespec_sub(&ts_end, &ts_start);
            us += ts_end.tv_nsec;
        }

        double avg = (double)us / cnt;
        double bw = 1e9 * (size * cnt) / us / (1<<20);
        snprintf(h2c_result, sizeof(h2c_result), "avg time: %.2f ns, bandwith: %.3f MB/s", avg, bw);
    });
}

void ZViewApp::SpeedTestForC2H()
{
    if (c2h.joinable())
        c2h.join();
    c2h = std::thread([this,cnt=test_cycle,size=test_size]{
        struct timespec ts_start, ts_end;
        unsigned long us = 0;
        for (int i=0; i<cnt; ++i) {
            clock_gettime(CLOCK_MONOTONIC, &ts_start);
            this->pDev->DmaC2H(this->pBuff2, (void*)(intptr_t)(i*size), size);
            clock_gettime(CLOCK_MONOTONIC, &ts_end);
            timespec_sub(&ts_end, &ts_start);
            us += ts_end.tv_nsec;
        }

        double avg = (double)us / cnt;
        double bw = 1e9 * (size * cnt) / us / (1<<20);
        snprintf(c2h_result, sizeof(c2h_result), "avg time: %.2f ns, bandwith: %.3f MB/s", avg, bw);
    });
}

uint32_t ZViewApp::UIFlags() const {
    return App::UIFlags() & ~UF_CONFIG;
}

void ZViewApp::OnKey(int key, int modifiers, bool is_press) {
    App::OnKey(key, modifiers, is_press);
}

void ZViewApp::OnWheelScroll(int dx, int dy) {
    App::OnWheelScroll(dx, dy);
}

DEFINE_FACTORY_FUNC(CreateZViewApp, window, args) {
    (void) args;
    return std::make_unique<ZViewApp>(window, "ZView Control");
}

REGISTER_APP_CREATOR("ZViewControl", &CreateZViewApp);
